package com.anlogic.sdk.utils;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

import org.apache.logging.log4j.Logger;
import org.eclipse.cdt.utils.spawner.ProcessFactory;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.Launch;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;

public class ProcLauncher implements IProcLauncher {
	private static Logger logger = LogUtils.getLogger(ProcLauncher.class);

	private Process process = null;

	private IProcess dprocess = null;

	private String[] environment;

	private final StringBuffer output = new StringBuffer();

	private void exec(String cmd) {
		logger.debug("Execute: " + cmd);
		String[] cmdarr = getCmdArr(cmd);
		if (cmdarr == null) {
			logger.error("ProcLauncher can not be initialized, command is null!");
			return;
		}
		this.process = null;
		try {
			File dir = new File(ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
			this.process = ProcessFactory.getFactory().exec(cmdarr, environment, dir);
		} catch (IOException e) {
			logger.error("Unable to execute {}", cmd);
			return;
		}
		Launch launch = new Launch(null, "run", null);
		this.dprocess = DebugPlugin.newProcess((ILaunch) launch, process, "ProcLauncher");

		IStreamMonitor stdOut = this.dprocess.getStreamsProxy().getOutputStreamMonitor();
		IStreamMonitor stdErr = this.dprocess.getStreamsProxy().getErrorStreamMonitor();

		stdOut.addListener(new IStreamListener() {
			public void streamAppended(String text, IStreamMonitor monitor) {
				 synchronized (output) {
					 output.append(text);
				 }
			}
		});

		stdErr.addListener(new IStreamListener() {
			public void streamAppended(String text, IStreamMonitor monitor) {
				 synchronized (output) {
					 output.append(text);
				 }
			}
		});
	}

	private String[] getCmdArr(String cmd) {
		if (cmd == null || cmd.isEmpty()) {
			return null;
		}
		String[] cmdarr = null;
		if (EnvUtils.isWindows()) {
			cmd = "cmd /c " + cmd;
		}
		String regex = " (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"; // Skip double-quoted strings
		cmdarr = cmd.split(regex);
		return cmdarr;
	}

	private int waitForExit() {
		if (this.process == null) {
			return EXEC_ERROR;
		}
		int exitval = INCOMPLETE_ERROR;
		try {
			exitval = this.process.waitFor();
		} catch (InterruptedException e) {
			return exitval;
		}
		return exitval;
	}

	public String launch(String cmd) {
		exec(cmd);
		int status = waitForExit();
		if (status != 0) {
			logger.error("{} abnormal exit! Exit status is: {}", cmd, status);
		}
		return flush();
	}

	public void asyncLaunch(String cmd) {
		exec(cmd);
	}

	public boolean hasTerminated() {
		if (this.dprocess != null)
			return this.dprocess.isTerminated();
		return true;
	}

	public void terminate() {
		try {
	        if (dprocess != null && !dprocess.isTerminated()) {
	            dprocess.terminate();
	        }
		} catch (DebugException e) {
			logger.error("Terminate failed", e);
		} finally {
	        if (process != null && process.isAlive()) {
	            process.destroyForcibly();
	        }
	        dprocess = null;
	        process = null;
	    }
	}

	public void setEnvironment(String[] environment) {
		 this.environment = Arrays.copyOf(environment, environment.length);
	}

	public String flush() {
	    String ret = "";
        for (int i = 0; i < 20; i++) {
            synchronized (output) {
                ret = output.toString();
            }
            if (!ret.isEmpty()) {
            	break;
            }
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
	    synchronized (output) {
	        output.setLength(0);
	    }
	    return ret;
	}
}